package middleware

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"time"

	"git.100tal.com/wangxiao_dahai_backend/arch-nova/utils"
	"github.com/gin-gonic/gin"
)

// 业务对接的context上下文不允许使用这些字段，否则会被强制修改

// 日志上下文
// 按网校日志格式定义
const (
	LogKeyName       = "x_name"
	LogKeyLevel      = "x_level"
	LogKeyTraceId    = "x_trace_id"
	LogKeyRpcId      = "x_rpc_id"
	LogKeyDepartment = "x_department"
	LogKeyModule     = "x_module"
	LogKeyVersion    = "x_version"
	LogKeyTimestamp  = "x_timestamp"
	LogKeyMsg        = "x_msg"
	LogKeyFile       = "x_file"
	LogKeyLine       = "x_line"
	LogKeyExtra      = "x_extra"
	LogKeyRequest    = "x_param"
	LogKeyResponse   = "x_response"
	LogKeyDuration   = "x_duration"
	// 自定义，非网校要求
	LogKeyHeader = "x_header"
)

// Http Header
const (
	HttpHeaderTraceId      = "X-Trace-ID"
	HttpHeaderRpcId        = "X-Rpc-ID"
	HeaderUpperAccessToken = "Dahai-Access-Token"
	HeaderLowerAccessToken = "dahai-access-token"
	HeaderBizProductLine   = "X-Trailer-Biz-Product-Line"
)

// 产品线
const (
	BizProductLineK12     = "k12" // k12
	BizProductForeign     = "ft"  // 外教
	BizProductLineK12New  = "1"   // k12
	BizProductForeignNew  = "2"   // 外教
	BizProductLineZhiKang = "3"   //智康
)

// 请求上下文
const (
	NvCtxRequestStart = "nv_ctx_request_start" // 请求开始时间
	NvCtxRequestSize  = "nv_ctx_request_size"  // 请求大小
	NvCtxRequestBody  = "nv_ctx_request_body"  // 请求Body
)

// 业务上下文
const (
	NvCtxUserId         = "nv_ctx_user_id"          // 用户id
	NvCtxAccessToken    = "nv_ctx_access_token"     // 认证token
	NvCtxBizProductLine = "nv_ctx_biz_product_line" // 产品线
)

// 日志记录请求响应数据大小上限，太长影响性能
const (
	LogRequestMaxSize  = 1024
	LogResponseMaxSize = 1024
)

// 初始化注入上下文参数
// gin.Context.Keys类型是map，不保证线程安全
// 在多个goroutine共享，存在冲突的风险
// https://github.com/gin-gonic/gin/issues/700
// 如果context需要在多个goroutine共享，使用context.Copy()
// 【备注】新的版本已通过读写锁 RWMutex 解决了这个问题
func HandlerInitContext() gin.HandlerFunc {
	return func(c *gin.Context) {
		setCtxRequestStart(c)
		setCtxRequestBody(c)
		setCtxRequestSize(c)

		setCtxTraceId(c)
		setCtxRpcId(c)

		setCtxLogRequest(c)
		setCtxLogExtra(c)

		setCtxAccessToken(c)

		c.Next()
	}
}

func GetCtxParam(c *gin.Context, key string) interface{} {
	value, exists := c.Get(key)
	if !exists {
		return nil
	}

	return value
}

func GetCtxParamInt(c *gin.Context, key string) (i int) {
	value := GetCtxParam(c, key)
	if value != nil {
		if v, ok := value.(int); ok {
			i = v
		}
	}
	return
}

func GetCtxParamString(c *gin.Context, key string) (s string) {
	value := GetCtxParam(c, key)
	if value != nil {
		if v, ok := value.(string); ok {
			s = v
		}
	}
	return
}

func GetCtxParamTime(c *gin.Context, key string) (t time.Time) {
	value := GetCtxParam(c, key)
	if value != nil {
		if v, ok := value.(time.Time); ok {
			t = v
		}
	}
	return
}

// 请求开始时间
func setCtxRequestStart(c *gin.Context) {
	c.Set(NvCtxRequestStart, time.Now())
}

// 请求数据
func setCtxRequestBody(c *gin.Context) {
	reqBody, _ := c.GetRawData()
	c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(reqBody))
	body := string(reqBody)
	c.Set(NvCtxRequestBody, body)
}

// 请求数据大小
func setCtxRequestSize(c *gin.Context) {
	s := computeApproximateRequestSize(c.Request)
	c.Set(NvCtxRequestSize, s)
}

// From https://github.com/DanielHeckrath/gin-prometheus/blob/master/gin_prometheus.go
func computeApproximateRequestSize(r *http.Request) int {
	s := 0
	if r.URL != nil {
		s = len(r.URL.Path)
	}

	s += len(r.Method)
	s += len(r.Proto)
	for name, values := range r.Header {
		s += len(name)
		for _, value := range values {
			s += len(value)
		}
	}
	s += len(r.Host)

	// N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.

	if r.ContentLength != -1 {
		s += int(r.ContentLength)
	}

	return s
}

// TraceId
func setCtxTraceId(c *gin.Context) {
	value := c.Request.Header.Get(HttpHeaderTraceId)
	if value == "" {
		value = utils.GetRandomUuid()
	}
	c.Set(LogKeyTraceId, value)
}

// RpcId
func setCtxRpcId(c *gin.Context) {
	value := c.Request.Header.Get(HttpHeaderRpcId)
	if value == "" {
		value = LogValueRpcId
	}
	c.Set(LogKeyRpcId, value)
}

// 日志打印请求
func setCtxLogRequest(c *gin.Context) {
	body := c.GetString(NvCtxRequestBody)
	c.Set(LogKeyRequest, body)
	// if len(body) < LogRequestMaxSize {
	// 	c.Set(LogKeyRequest, body)
	// } else {
	// 	c.Set(LogKeyRequest, string([]rune(body)[:LogRequestMaxSize]))
	// }
}

// 日志额外信息
func setCtxLogExtra(c *gin.Context) {
	if logExtra == nil {
		return
	}

	extra := &LogExtra{
		AppName:   logExtra.AppName,
		Env:       logExtra.Env,
		Mode:      logExtra.Mode,
		Version:   logExtra.Version,
		Namespace: logExtra.Namespace,
		PodName:   logExtra.PodName,
		PodIp:     logExtra.PodIp,
		Uri:       c.Request.RequestURI,
	}
	log, err := json.Marshal(extra)
	if err != nil {
		c.Set(LogKeyExtra, LogValueExtra)
	} else {
		c.Set(LogKeyExtra, string(log))
	}
}

func setCtxAccessToken(c *gin.Context) {
	accessToken := c.GetHeader(HeaderUpperAccessToken)
	if accessToken != "" {
		c.Set(NvCtxAccessToken, accessToken)
	} else {
		accessToken = c.GetHeader(HeaderLowerAccessToken)
		if accessToken != "" {
			c.Set(NvCtxAccessToken, accessToken)
		}
	}
}

func GetDahaiAccessToken(c *gin.Context) string {
	if c.Request != nil {
		return c.GetHeader(HeaderUpperAccessToken)
	} else {
		return ""
	}
}

func GetBizProductLine(c *gin.Context) string {
	if c.Request != nil {
		return c.GetHeader(HeaderBizProductLine)
	} else {
		return ""
	}
}
